package com.geek.rest.rs;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import static java.lang.String.format;


public class HttpPostInvocation implements Invocation {

    public static final String CONTENT_ENCODING = "Content-Encoding";
    /**
     * Value for the Content-Encoding header that indicates that GZIP encoding is in use.
     */
    public static final String ENCODING_GZIP = "gzip";
    /**
     * Value for the Content-Encoding header that indicates that DEFLATE encoding is in use.
     */
    public static final String ENCODING_DEFLATE = "deflate";
    /**
     * The HTTP Content-Length header field name.
     */
    public static final String CONTENT_LENGTH = "Content-Length";

    private final URL url;

    private final Entity<?> entity;

    private final MultivaluedMap<String, Object> headers;

    public HttpPostInvocation(URI uri, Entity<?> entity, MultivaluedMap<String, Object> headers) {

        this.entity = entity;
        this.headers = headers;
        try {
            this.url = uri.toURL();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public Invocation property(String name, Object value) {
        return this;
    }

    /**
     * copy feign.Client
     * @return
     */
    @Override
    public Response invoke() {
        HttpURLConnection connection = convertAndSent();
        return convertResponse(connection);
    }

    private Response convertResponse(HttpURLConnection connection) {
        try {
            int status = connection.getResponseCode();
            if (status < 0) {
                throw new IOException(format("Invalid status(%s) executing %s %s", status,
                        connection.getRequestMethod(), connection.getURL()));
            }
            MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
            for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
                if (entry.getKey() != null) {
                    headers.put(entry.getKey(), entry.getValue());
                }
            }
            String contentEncoding = Optional.ofNullable(connection.getContentEncoding()).orElse(Charset.defaultCharset().name());

            InputStreamReader streamReader = new InputStreamReader(connection.getInputStream(), contentEncoding);
            //{"message":"Shutting down, bye..."}
            String string = IOUtils.toString(streamReader);

            DefaultResponse response = new DefaultResponse()
                    .setConnection(connection)
                    .setStatus(status)
                    .setHeaders(headers)
                    .setEntity(string);
            return response;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    public HttpURLConnection convertAndSent() {
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod(HttpMethod.POST);

            List<Object> list = headers.get(CONTENT_ENCODING);
            boolean gzipEncodedRequest = false;
            boolean deflateEncodedRequest = false;
            boolean hasAcceptHeader = false;
            Integer contentLength = null;
            if (list != null) {
                List<String> contentHeader = list.stream().map(String::valueOf).collect(Collectors.toList());
                gzipEncodedRequest = contentHeader.contains(ENCODING_GZIP);
                deflateEncodedRequest = contentHeader.contains(ENCODING_DEFLATE);
            }
            for (Map.Entry<String, List<Object>> entry : headers.entrySet()) {
                if (entry.getKey().equalsIgnoreCase("Accept")) {
                    hasAcceptHeader = true;
                }
                for (Object o : entry.getValue()) {
                    String headerValue = String.valueOf(o);
                    if (headerValue.equalsIgnoreCase(CONTENT_LENGTH)) {
                        if (!gzipEncodedRequest && !deflateEncodedRequest) {
                            contentLength = Integer.valueOf(headerValue);
                            connection.addRequestProperty(entry.getKey(), headerValue);
                        }
                    } else {
                        connection.addRequestProperty(entry.getKey(), headerValue);
                    }
                }
            }

            if (!hasAcceptHeader) {
                connection.addRequestProperty("Accept", "*/*");
            }

            if (entity != null) {
                if (contentLength != null) {
                    connection.setFixedLengthStreamingMode(contentLength);
                } else {
                    connection.setChunkedStreamingMode(8196);
                }
                connection.setDoOutput(true);
                OutputStream out = connection.getOutputStream();
                if (gzipEncodedRequest) {
                    out = new GZIPOutputStream(out);
                } else if (deflateEncodedRequest) {
                    out = new DeflaterOutputStream(out);
                }
                try {
                    String body = new ObjectMapper().writeValueAsString(entity.getEntity());
                    out.write(body.getBytes());
                } finally {
                    try {
                        out.close();
                    } catch (IOException suppressed) { // NOPMD
                    }
                }
            }

        } catch (Exception e) {
        }
        return connection;
    }


    @Override
    public <T> T invoke(Class<T> responseType) {
        if (responseType == String.class) {
            return (T) String.valueOf(invoke().getEntity());
        }
        Response invoke = invoke();
        try {
            return new ObjectMapper().readValue(String.valueOf(invoke.getEntity()), responseType);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> T invoke(GenericType<T> responseType) {
        return null;
    }

    @Override
    public Future<Response> submit() {
        return null;
    }

    @Override
    public <T> Future<T> submit(Class<T> responseType) {
        return null;
    }

    @Override
    public <T> Future<T> submit(GenericType<T> responseType) {
        return null;
    }

    @Override
    public <T> Future<T> submit(InvocationCallback<T> callback) {
        return null;
    }
}
